package jamirr;

import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;

public interface BeanMirror<B> extends AnnotatedElement, BeanInfo<B> {

    /**
     * @return 一个不可变的构造函数集合。
     */
    Collection<ConstructorMirror<B>> getConstructors();

    default Optional<ConstructorMirror<B>> getConstructor(Class<?>... parameterTypes) {
        if (parameterTypes == null) {
            parameterTypes = new Class<?>[0];
        }

        final Class<?>[] finalParameterTypes = parameterTypes;
        return getConstructors().stream()
                .filter(ctor -> ctor.matches(finalParameterTypes))
                .findFirst();
    }

    default ConstructorMirror<B> getRequiredConstructor(Class<?>... parameterTypes) {
        return getConstructor(parameterTypes)
                .orElseThrow(() -> new MirrorException("No constructor with parameters of type " + Arrays.toString(parameterTypes) + "] found."));
    }

    /**
     * @return 一个不可变的属性集合。
     */
    Collection<PropertyMirror<B, Object>> getProperties();

    /**
     * 以数组形式返回属性名称。
     *
     * @return 属性名称
     */
    default String[] getPropertyNames() {
        return getProperties().stream()
                .map(PropertyMirror::getName)
                .toArray(String[]::new);
    }

    /**
     * 根据名称获取属性。
     *
     * @param name 属性的名称
     * @return 如果找到则返回一个bean属性
     */
    default Optional<PropertyMirror<B, Object>> getProperty(String name) {
        return getProperties().stream()
                .filter(p -> p.getName().equals(name))
                .findFirst();
    }

    /**
     * 获取给定名称和类型的属性，或在属性不存在时抛出{@link MirrorException}。
     *
     * @param name 名称
     * @param type 类型
     * @param <P>  属性的泛型类型
     * @return 属性
     */
    default <P> PropertyMirror<B, P> getRequiredProperty(String name, Class<P> type) {
        return getProperty(name, type)
                .orElseThrow(() -> new MirrorException("No property [" + name + "] of type [" + type + "] present"));
    }

    /**
     * 根据名称和类型获取属性。
     *
     * @param name 属性的名称
     * @param type 要搜索的属性类型
     * @param <P>  属性的类型
     * @return 如果找到则返回一个bean属性
     */
    @SuppressWarnings("unchecked")
    default <P> Optional<PropertyMirror<B, P>> getProperty(String name, Class<P> type) {
        Objects.requireNonNull(name, "name cannot be null");
        Objects.requireNonNull(type, "type cannot be null");

        final PropertyMirror<B, ?> prop = getProperty(name).orElse(null);
        if (prop != null && type.isAssignableFrom(prop.getPropertyType())) {
            return Optional.of((PropertyMirror<B, P>) prop);
        }

        return Optional.empty();
    }

    /**
     * 获取属性的索引位置。
     *
     * @param name 属性的名称
     * @return 属性的索引或如果未找到则为-1。
     */
    default int propertyIndexOf(String name) {
        int index = 0;
        for (PropertyMirror<B, Object> property : getProperties()) {
            if (property.getName().equals(name)) {
                return index;
            }
            index++;
        }
        return -1;
    }

    /**
     * 返回此内省的{@link MethodMirror}实例。
     *
     * @return 一个不可变的方法集合。
     */
    Collection<MethodMirror<B, Object>> getMethods();

    @SuppressWarnings("unchecked")
    default <R> Optional<MethodMirror<B, R>> getMethod(String name, Class<?>... parameterTypes) {
        Objects.requireNonNull(name, "name cannot be null");

        if (parameterTypes == null) {
            parameterTypes = new Class<?>[0];
        }

        final Class<?>[] finalParameterTypes = parameterTypes;
        return getMethods().stream()
                .filter(m -> m.getName().equals(name))
                .filter(m -> m.matches(finalParameterTypes))
                .findFirst()
                .map(m -> (MethodMirror<B, R>) m);
    }

    @SuppressWarnings("unchecked")
    default <R> MethodMirror<B, R> getRequiredMethod(String name, Class<?>... parameterTypes) {
        return (MethodMirror<B, R>) getMethod(name, parameterTypes)
                .orElseThrow(() -> new MirrorException("No method [" + name + "] with parameters of type " + Arrays.toString(parameterTypes) + "] found."));
    }

    static <T> BeanMirror<T> of(Class<T> type) {
        return BeanReferenceLoader.INSTANCE.getBeanMirror(type);
    }
}
